﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;

namespace Lsa
{
    public enum Privileges
    {
        SeAssignPrimaryTokenPrivilege,
        SeAuditPrivilege,
        SeBackupPrivilege,
        SeBatchLogonRight,
        SeChangeNotifyPrivilege,
        SeCreateGlobalPrivilege,
        SeCreatePagefilePrivilege,
        SeCreatePermanentPrivilege,
        SeCreateSymbolicLinkPrivilege,
        SeCreateTokenPrivilege,
        SeDebugPrivilege,
        SeImpersonatePrivilege,
        SeIncreaseBasePriorityPrivilege,
        SeIncreaseQuotaPrivilege,
        SeInteractiveLogonRight,
        SeLoadDriverPrivilege,
        SeLockMemoryPrivilege,
        SeMachineAccountPrivilege,
        SeManageVolumePrivilege,
        SeNetworkLogonRight,
        SeProfileSingleProcessPrivilege,
        SeRemoteInteractiveLogonRight,
        SeRemoteShutdownPrivilege,
        SeRestorePrivilege,
        SeSecurityPrivilege,
        SeServiceLogonRight,
        SeShutdownPrivilege,
        SeSystemEnvironmentPrivilege,
        SeSystemProfilePrivilege,
        SeSystemtimePrivilege,
        SeTakeOwnershipPrivilege,
        SeTcbPrivilege,
        SeTimeZonePrivilege,
        //SeUnsolicitedInputPrivilege
        SeUndockPrivilege,
        SeDenyNetworkLogonRight,
        SeDenyBatchLogonRight,
        SeDenyServiceLogonRight,
        SeDenyInteractiveLogonRight,
        SeSyncAgentPrivilege,
        SeEnableDelegationPrivilege,
        SeDenyRemoteInteractiveLogonRight,
        SeTrustedCredManAccessPrivilege,
        SeIncreaseWorkingSetPrivilege
    }

    public sealed class LsaLib
    {
        public static string[] EnumerateAccountsWithRight(string computerName, Privileges privilege)
        {
            UInt32 ntStatus;

            LSA_UNICODE_STRING computer = new LSA_UNICODE_STRING();
            computer.Buffer = computerName;
            computer.Length = (UInt16)(computer.Buffer.Length * UnicodeEncoding.CharSize);
            computer.MaximumLength = (UInt16)((computer.Buffer.Length + 1) * UnicodeEncoding.CharSize);

            LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
            IntPtr policyHandle;
            ntStatus = Win32Lsa.LsaOpenPolicy(ref computer, ref ObjectAttributes, (uint)(LsaAccessPolicy.POLICY_LOOKUP_NAMES | LsaAccessPolicy.POLICY_VIEW_LOCAL_INFORMATION), out policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new OpenLsaPolicyException((int)ntStatus);
            }

            LSA_UNICODE_STRING Privilege = new LSA_UNICODE_STRING();
            Privilege.Buffer = privilege.ToString();
            Privilege.Length = (UInt16)(Privilege.Buffer.Length * UnicodeEncoding.CharSize);
            Privilege.MaximumLength = (UInt16)((Privilege.Buffer.Length + 1) * UnicodeEncoding.CharSize);

            IntPtr enumerationBuffer;
            UInt32 countReturned;
            ntStatus = Win32Lsa.LsaEnumerateAccountsWithUserRight(policyHandle, ref Privilege, out enumerationBuffer, out countReturned);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                if (ntStatus == 259)
                {
                    return new string[0];
                }

                Win32Lsa.LsaClose(policyHandle);
                throw new AccountUserRightsEnumerationException((int)ntStatus);
            }
            LSA_ENUMERATION_INFORMATION sid = new LSA_ENUMERATION_INFORMATION();

            UInt32 StructSize = (UInt32)Marshal.SizeOf(typeof(LSA_ENUMERATION_INFORMATION));
            IntPtr enumerationItem;

            var stringSids = new List<string>();
            for (int i = 0; i < countReturned; i++)
            {
                enumerationItem = (IntPtr)(enumerationBuffer.ToInt64() + (StructSize * i));
                sid = (LSA_ENUMERATION_INFORMATION)(Marshal.PtrToStructure(enumerationItem, typeof(LSA_ENUMERATION_INFORMATION)));

                var stringSid = String.Empty;
                Win32Lsa.ConvertSidToStringSid(sid.Sid, out stringSid);

                stringSids.Add(stringSid);
                //Win32Lsa.FreeSid(sid.Sid);
            }

            ntStatus = Win32Lsa.LsaClose(policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new CloseLsaPolicyException((int)ntStatus);
            }

            //Win32Lsa.LsaFreeMemory(enumerationBuffer);
            return stringSids.ToArray();
        }

        public static string[] GetAccountRights(string computerName, SecurityIdentifier si)
        {
            UInt32 ntStatus;

            IntPtr sid = IntPtr.Zero;

            Win32Lsa.ConvertStringSidToSid(si.Value, ref sid);

            LSA_UNICODE_STRING computer = new LSA_UNICODE_STRING();
            computer.Buffer = computerName;
            computer.Length = (UInt16)(computer.Buffer.Length * UnicodeEncoding.CharSize);
            computer.MaximumLength = (UInt16)((computer.Buffer.Length + 1) * UnicodeEncoding.CharSize);

            LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
            IntPtr policyHandle;
            ntStatus = Win32Lsa.LsaOpenPolicy(ref computer, ref ObjectAttributes, (uint)(LsaAccessPolicy.POLICY_LOOKUP_NAMES | LsaAccessPolicy.POLICY_VIEW_LOCAL_INFORMATION), out policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new OpenLsaPolicyException((int)ntStatus);
            }

            int countOfRights = 0;
            IntPtr userRightsPtr = IntPtr.Zero;
            ntStatus = Win32Lsa.LsaEnumerateAccountRights(policyHandle, sid, out userRightsPtr, out countOfRights);
            //Marshal.FreeHGlobal(sid);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                if (ntStatus == 2)
                {
                    return new string[0];
                }

                Win32Lsa.LsaClose(policyHandle);
                throw new AccountUserRightsEnumerationException((int)ntStatus);
            }

            LSA_UNICODE_STRING userRight;
            var userRights = new string[countOfRights];

            for (int i = 0; i < countOfRights; i++)
            {
                userRight = (LSA_UNICODE_STRING)Marshal.PtrToStructure(userRightsPtr, typeof(LSA_UNICODE_STRING));
                userRights[i] = userRight.Buffer;

                userRightsPtr += Marshal.SizeOf(userRight);
            }

            ntStatus = Win32Lsa.LsaClose(policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new CloseLsaPolicyException((int)ntStatus);
            }

            //Win32Lsa.FreeSid(sid);
            //Win32Lsa.LsaFreeMemory(userRightsPtr);

            return userRights;
        }

        public static void AddAccountRights(string computerName, SecurityIdentifier si, Privileges privilege)
        {
            UInt32 ntStatus;

            IntPtr sid = IntPtr.Zero;

            Win32Lsa.ConvertStringSidToSid(si.Value, ref sid);

            LSA_UNICODE_STRING computer = new LSA_UNICODE_STRING();
            computer.Buffer = computerName;
            computer.Length = (UInt16)(computer.Buffer.Length * UnicodeEncoding.CharSize);
            computer.MaximumLength = (UInt16)((computer.Buffer.Length + 1) * UnicodeEncoding.CharSize);

            LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
            IntPtr policyHandle;
            ntStatus = Win32Lsa.LsaOpenPolicy(ref computer, ref ObjectAttributes, (uint)(LsaAccessPolicy.POLICY_LOOKUP_NAMES | LsaAccessPolicy.POLICY_CREATE_ACCOUNT), out policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new OpenLsaPolicyException((int)ntStatus);
            }

            LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
            userRights[0] = new LSA_UNICODE_STRING();
            userRights[0].Buffer = privilege.ToString();
            userRights[0].Length = (UInt16)(userRights[0].Buffer.Length * UnicodeEncoding.CharSize);
            userRights[0].MaximumLength = (UInt16)((userRights[0].Buffer.Length + 1) * UnicodeEncoding.CharSize);

            ntStatus = Win32Lsa.LsaAddAccountRights(policyHandle, sid, userRights, 1);

            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                Win32Lsa.LsaClose(policyHandle);
                throw new AddAccountRightsException((int)ntStatus);
            }

            ntStatus = Win32Lsa.LsaClose(policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new CloseLsaPolicyException((int)ntStatus);
            }
        }

        public static void RemoveAccountRights(string computerName, SecurityIdentifier si, Privileges privilege)
        {
            UInt32 ntStatus;

            IntPtr sid = IntPtr.Zero;

            Win32Lsa.ConvertStringSidToSid(si.Value, ref sid);

            LSA_UNICODE_STRING computer = new LSA_UNICODE_STRING();
            computer.Buffer = computerName;
            computer.Length = (UInt16)(computer.Buffer.Length * UnicodeEncoding.CharSize);
            computer.MaximumLength = (UInt16)((computer.Buffer.Length + 1) * UnicodeEncoding.CharSize);

            LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
            IntPtr policyHandle;
            ntStatus = Win32Lsa.LsaOpenPolicy(ref computer, ref ObjectAttributes, (uint)(LsaAccessPolicy.POLICY_LOOKUP_NAMES | LsaAccessPolicy.POLICY_CREATE_ACCOUNT), out policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new OpenLsaPolicyException((int)ntStatus);
            }

            LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
            userRights[0] = new LSA_UNICODE_STRING();
            userRights[0].Buffer = privilege.ToString();
            userRights[0].Length = (UInt16)(userRights[0].Buffer.Length * UnicodeEncoding.CharSize);
            userRights[0].MaximumLength = (UInt16)((userRights[0].Buffer.Length + 1) * UnicodeEncoding.CharSize);

            ntStatus = Win32Lsa.LsaRemoveAccountRights(policyHandle, sid, false, userRights, 1);

            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                Win32Lsa.LsaClose(policyHandle);
                throw new AddAccountRightsException((int)ntStatus);
            }

            ntStatus = Win32Lsa.LsaClose(policyHandle);
            if (ntStatus != Win32Lsa.STATUS_SUCCESS)
            {
                ntStatus = Win32Lsa.LsaNtStatusToWinError(ntStatus);
                throw new CloseLsaPolicyException((int)ntStatus);
            }
        }
    }
}